#Written by: Arun Balasubramanian
#This code simulates the logic for local and internode preemptive scheduling for CAN peer to peer technique in remote desktop grids
#This is done by invoking a separate thread for functionality within a node. The communication between nodes is implemented through interthread communication
import time
import thread
import sys
import random

alpha=5000
class  job:
	def __init__(self, jobid=None, est_time=None, cpu_speed=None, memory=None, disk_size=None, wait_time=None, submit_time=None, p_job=None, cores=None, job_finishtime=None):
		if jobid==None:
			self.jobid = -1
			self.est_time = 0
			self.cpu_speed = 0
			self.memory = 0
			self.disk_size = 0
			self.wait_time = 0
			self.submit_time = 0
			self.p_job = 0
			self.cores = 0
			self.job_finishtime = 0
			self.inqueue=0
			self.running=0
			self.counter=0
			self.chosen = 0
			self.completed =0
			self.total_runtime=0
		else: 	
			self.jobid = jobid
			self.est_time = float(est_time)
			self.cpu_speed = cpu_speed
			self.memory = memory 
			self.disk_size = disk_size
			self.wait_time = float(wait_time)
			self.submit_time = float(submit_time)
			self.p_job = float(p_job)
			self.cores = int(cores)
			self.job_finishtime = float(job_finishtime)
			self.inqueue=0
			self.running=0
			self.counter=0
			self.chosen = 0
			self.completed=0
			self.total_runtime=0

class neighbor:
	def __init__(self, node=None, top_job=None, min_job=None):
		if node!=None:
			self.node = node
			self.top_job = top_job
			self.min_job = min_job
		else:
			self.node = node()
			self.top_job = job()
			self.min_job = job()
				
		 	
class node:
	def __init__(self, nodeID=None, cpu_speed=None, memory=None, disk_size=None, join_time=None, cores=None, global_time=None):
		if nodeID!=None:
			self.nodeID = nodeID
			self.jobs = []
			self.cpu_speed = cpu_speed
			self.memory = memory
			self.disk_size = disk_size
			self.join_time = join_time
			self.cores = int(cores)
			self.global_time = float(global_time)
			self.curr_jobs = 0
			self.res_cores = int(cores)
			self.nbors = []
		else:
			self.nodeID = -1
			self.jobs = []
			self.cpu_speed = 0
			self.memory = 0
			self.disk_size = 0
			self.join_time = 0
			self.cores = 0
			self.global_time = 0
			self.curr_jobs = 0
			self.res_cores = 0
			self.nbors = []

	def fetch_next_job(self):
		maxp_job=0
		top = job()
		for j in self.jobs:
			if j.inqueue==1:
				if maxp_job < j.p_job:
					maxp_job = j.p_job
					top = j
		for j in self.jobs:
			if top.jobid == j.jobid:
				j.inqueue=0
		return top
		
#	def append_job(self, jobid):
#		self.jobid.append(jobid)	
	def calculate_priority(self, job):
#		for jid in jobid:
		#	for j in self.jobs:
		#		if j.inqueue == 1 or j.running ==1:		
		#			if self.global_time >= j.submit_time:
		job.p_job = 1 + (alpha * (job.wait_time/job.est_time))
		return 

	def preempt(self, job1):
		job1.running=0
		job1.inqueue=1
		self.res_cores += job1.cores
		self.curr_jobs -= 1
		job1.chosen=0
		job1.est_time -= (self.global_time - job1.counter)
		job1.total_runtime += self.global_time - job1.counter
		job1.counter = self.global_time
		if job1.est_time <=0:
			job1.completed=1 	
		return

	def min_currjobs(self):
		min_currjob = job()
		minp=1000000
		for j in self.jobs:
			if j.running ==1:
	#			print "RUNNING JOB"
				if j.chosen==0:
					if minp > j.p_job:
						min_currjob = j
						minp = j.p_job
						
		for j in self.jobs:
				if min_currjob.jobid == j.jobid:
					j.chosen=1
#		print "minjob details"+ str(min_currjob.jobid)
		return min_currjob						

	def push(self,nbor_node,push_job):
		print "INSIDE PUSH"
		nbor_node.jobs.append(push_job.jobid, push_job.est_time, push_job.cpu_seed, push_job.memory, push_job.disk_size, push_job.wait_time, push_job.submit_time, push_job.p_job, push_job.cores, push_job.job_finishtime);
		nbor_node.curr_jobs+=1
		nbor_node.res_cores-=push_job.cores
		for j in nbor_node.jobs:
			if j.jobID== push_job.jobID:
				j.running=1
				print "JOB: "+str(push_job.jobID)+"PUSHED from "+str(self.nodeID)+" to"+str(nbor_node.nodeID)
		for j in self.jobs:
			if j.jobID==push_job.jobID:
				j.inqueue=0
				j.completed=1
				
	def pull(self, nbor_node, pull_job):
		self.jobs.append(pull_job.jobid, pull_job.est_time, pull_job.cpu_seed, pull_job.memory, pull_job.disk_size, pull_job.wait_time, pull_job.submit_time, pull_job.p_job, pull_job.cores, pull_job.job_finishtime);
		self.curr_jobs+=1
		self.res_cores-=pull_job.cores
		for j in self.jobs:
			if j.jobID== pull_job.jobID:
				j.running=1
		for j in nbor_node.jobs:
			if j.jobID==pull_job.jobID:
				j.inqueue=0
				j.completed=1
				
	def scheduler(self):
		#the scheduler performs the following scheduling operation everytime it is invoked
		jobs_considered = []
		i=0
	#	j = job()
		for jid in self.jobs:
			if jid.inqueue==1 or jid.running==1:
				jid.chosen=0
				self.calculate_priority(jid) 
		if self.curr_jobs == 0:
			j=self.fetch_next_job()
		#	print "REMAINING CORES"+str(self.res_cores)+"REQUIRED CORES"+str(j.cores)+"NODEID"+str(self.nodeID)+"CORES"+str(self.cores)
		#	if self.res_cores >= j.cores:
			if j.jobid !=-1:
				#index = self.jobs.index(self.fetch_next_job)
				j.running = 1
				j.inqueue = 0
				#j.inqueue = 0
				self.curr_jobs+=1
				self.res_cores -= j.cores 
				j.counter = self.global_time
		else:
	#	elif self.curr_jobs > 0:
	#		print "INSIDE SCHEDULER"
			j=self.fetch_next_job()
			minjob = self.min_currjobs()
		#	print "MINJOB.JOBID:"+str(minjob.jobid)
			temp_cores = self.res_cores
			while j.p_job > minjob.p_job and minjob.jobid!=-1:
	#			print "INSIDE WHILE ********************************************************************"
				if temp_cores >= j.cores:
					for j1 in jobs_considered:
						self.preempt(j1)	
					j.running = 1
					j.inqueue = 0
					self.res_cores -= j.cores
					self.curr_jobs+=1
					j=self.fetch_next_job()
					temp_cores = self.res_cores
					minjob = self.min_currjobs()
					jobs_considered = []
				else:
					jobs_considered.append(minjob)
					temp_cores+=minjob.cores
					if minjob.cores>= j.cores:
						self.preempt(minjob)
						j.running = 1
						j.inqueue = 0
						self.res_cores -= j.cores
						self.curr_jobs+=1
						j=self.fetch_next_job()
						temp_cores = self.res_cores
						jobs_considered = []
						minjob = self.min_currjobs()
					elif temp_cores >= j.cores:
						for j1 in jobs_considered:
							self.preempt(j1)
						jobs_considered=[]	
						j.running = 1
						j.inqueue = 0
						self.res_cores -= j.cores
						self.curr_jobs+=1
						j=self.fetch_next_job()
						temp_cores = self.res_cores
					else:					
						minjob = self.min_currjobs()
		#self.backfilling()
		self.update_neighbors()
		#Find the node to push the job
		temp_pjob = 0
		flag=0
		flag_for_push=0
		dest_node=neighbor(node(),job(),job())
		for nbor in self.nbors:
			if nbor.top_job.p_job < j.p_job:
				if dest_node.top_job.p_job > nbor.top_job.p_job: 	
					if nbor.node.res_cores >= j.cores:
						dest_node.node=nbor.node
						flag=1
		if flag==1:
			self.push(dest_node,j)
		flag=0
		for nbor in self.nbors:
			if nbor.top_job.p_job > j.p_job:
				if dest_node.top_job.p_job < nbor.top_job.p_job: 	
					if self.res_cores >= nbor.top_job.cores:
						dest_node.node=nbor.node
						flag=1
		if flag==1:
				self.pull(dest_node,dest_node.top_job)
		
		
		return
		
	def add_neighbors(self):
		for x in range(1,4):
			nbor_nodeID = random.randint(1,1000)		#generate a random neighbor
			for new_node in nodeList:
			#	print "Comparing "+str(new_node.nodeID)+"and "+str(nbor_nodeID)
				if str(new_node.nodeID) == str(nbor_nodeID):
					temp_topjob = new_node.fetch_next_job()
					temp_minjob = new_node.min_currjobs()
					print "Neighbor "+str(new_node.nodeID)+"added"
					self.nbors.append(neighbor(new_node,temp_topjob, temp_minjob))
		return				 
		
	def update_neighbors(self):
		for nbor in self.nbors:
			for node1 in nodeList:
				if str(node1.nodeID) == str(nbor.node.nodeID):
					nbor.node = node1
					nbor.top_job = node1.fetch_next_job()
					nbor.min_job = node1.min_currjobs()
		return


	def jobs_completed(self):
		flag=0
		for j in self.jobs:
			if j.completed==0:
			#	print "jobid:"+str(j.jobid)+"Est time:"+str(j.est_time)+"running status:"+str(j.running)+"queue status"+str(j.inqueue)
				flag=1
		if flag==1:
			return 0
		else:
			return 1
			
			

fp = open("1-IAT1.5S-CC.in", "r")
line = fp.readline()
buff = list()
#string, nodeID, jobid, cpu_speed, memory, disk_size, join_time, cores, global_time = 
buff = line.split(' ')
#print buff[0]
nodeList = []
jobList = []
mark=0
while buff[0] != "exit":
	mark=0
	if buff[0] == "join":
		nodeList.append(node(buff[1],buff[5], buff[6], buff[7], buff[9], buff[8], 0))
	elif buff[0] == "submitJob":
		for n in nodeList:
			if n.nodeID == buff[1]:
				if n.cores >= int(buff[9]):
					if len(n.jobs) < 8:
						n.jobs.append(job(buff[2], buff[3], buff[6], buff[7], buff[8], 0, buff[10], 0, buff[9], 0))
					mark=1
					break
		if mark==0:
			for n1 in nodeList:
				if int(n1.nodeID) != int(buff[1]):
					if n1.cores >= int(buff[9]):
						if len(n1.jobs) < 8:
							n1.jobs.append(job(buff[2], buff[3], buff[6], buff[7], buff[8], 0, buff[10], 0, buff[9], 0))
							break
						
		
				
	line = fp.readline()
	buff = line.split(' ')
#	print buff[0]
count=0
for n in nodeList:
	print "NODEID: "+ str(n.nodeID)
	count+=1
	n.add_neighbors()
print "Count:"+ str(count)
count = 0
jobs_over=0
while jobs_over==0:
	jobs_over=1
	for n in nodeList:
	#	print "\nstarted: "+str(n.nodeID)
		if n.jobs_completed()==0:
			count=0
			tot_jobs=0
			n.global_time += 150000
		#	print "GLOBAL TIME: "+ str(n.global_time)
			for j in n.jobs:
				if j.running == 1:		#if the job is currently running in queue, check its status and perform necessary actions
					j.total_runtime += (n.global_time - j.counter)
					j.est_time -= n.global_time - j.counter
					j.counter = n.global_time
				#	print "Est TIME of job:"+j.jobid+"is: "+str(j.est_time)
					if j.est_time <= 0:	#if the job is completed, mark as complete and invoke the scheduler
						j.running=0
						j.inqueue=0
						j.completed=1
						n.curr_jobs -= 1
						n.res_cores += j.cores	#free up the cores
						n.scheduler()
				elif j.inqueue==1:		# if the job is waiting in queue, update its waiting time
					j.wait_time = (n.global_time - j.submit_time) - j.total_runtime
		#			print "Est TIME of job:"+j.jobid+"is: "+str(j.est_time)
				elif ((j.submit_time < n.global_time) and (j.inqueue==0 and j.completed==0)): 	# if the job has arrived, push it in queue and update the wait time
	#				print "INSIAD"
					j.inqueue=1
					j.wait_time = n.global_time - j.submit_time
				#self.calculate_priority(j)			#calculate the priority of jobs
					n.scheduler()
		
			#self.calculate_priority()			#calculate the priority of jobs
			for j in n.jobs:
				if j.completed==1:
					count+=1
				elif str(n.nodeID)==str(714):
					print "jobid:"+str(j.jobid)+"Est time:"+str(j.est_time)+"running status:"+str(j.running)+"queue status"+str(j.inqueue)
				tot_jobs+=1
			if str(n.nodeID)==str(714):
				print "Total jobs:"+str(tot_jobs)+"Finished jobs:"+str(count)
				
			n.scheduler()	
		#print "\nend of while loop"			
		count +=1
	for n1 in nodeList:
		if n.jobs_completed()==0:
			jobs_over=0
			

count=0
wait_time=0
for n in nodeList:
	for j in n.jobs:
		wait_time += j.wait_time
	
	count+=1
print "Average wait time:"+ str(wait_time/count)


#except:
#	print "Error:Unable to start"

 

